// CadEngine.cpp
//
#include "CadEngine.h"

#include "CadTool.h"

#include "Geometry/VObjectManager.h"
#include "Geometry/VPoint.h"
#include "Geometry/VLine.h"
#include "Geometry/VPolyLine.h"
#include "Geometry/VRectangle.h"
#include "Geometry/VCircle.h"

#include "OpenGL/OpenGL.h"
#include "OpenGL/Renderer.h"

#include <iostream>

#include "StringTools.h"
#include "MemCheck.h"

// ------------------------------------------------------------------------------------------------
CadEngine::CadEngine( VObjectManager *objManager )
: _objManager(objManager),
  _currTool(0),
  _pixelSize(1.0),
  _worldSize(1.0),
  _rubberband(false)
{}

// ------------------------------------------------------------------------------------------------
CadEngine::~CadEngine()
{
	for( CadTools::iterator it = _tools.begin();
		 it != _tools.end();
		 ++it )
	{
		delete it->second;
	}

	_tools.clear();
}

// ------------------------------------------------------------------------------------------------
void CadEngine::init()
{
	// @todo: load from settings
	_settings.setDefaultObjColor( OpenGL::Color::blue().lighter(.25f) );
	_settings.setHilightedColor ( OpenGL::Color::yellow().lighter() );
	_settings.setPreviewColor   ( OpenGL::Color::white() );
	_settings.setSelectedColor  ( OpenGL::Color::magenta() );
	_settings.setSnapPointColor ( OpenGL::Color::green().lighter(.25f) );

	VObject::setHilightColor( _settings.hilightedColor() );
	VObject::setSelectionColor( _settings.selectedColor() );
	_snapEngine.setColor( _settings.snapPointColor() );
}


// ------------------------------------------------------------------------------------------------
void CadEngine::setCurrentTool( CadTool *tool )	// tool chain
{}


// ------------------------------------------------------------------------------------------------
int CadEngine::addTool( CadTool *tool )
{
	static unsigned int id = 0;

	if( !tool )
		return -1;

	tool->setManager(this);

	_tools[id] = tool;
	
	return id++;
}

// ------------------------------------------------------------------------------------------------
void CadEngine::draw( OpenGL::Renderer *r )
{
	if( isEnabled() )
	{
		glPushAttrib( GL_ALL_ATTRIB_BITS );

		glEnable(GL_POINT_SMOOTH);
		glEnable(GL_LINE_SMOOTH);
		glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
		glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

		glDepthMask(GL_FALSE);
		glDisable(GL_DEPTH_TEST);
	
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); 		
		

		_currTool->draw( r );

		if( _currTool->canSnap() )
			_snapEngine.draw( r );

		glPopAttrib();
	}
}

// ------------------------------------------------------------------------------------------------
CadTool *CadEngine::getCurrentTool() const	{ return _currTool; }

// ------------------------------------------------------------------------------------------------
CadTool *CadEngine::tool( int toolID )
{
	CadTools::iterator it = _tools.find( toolID );

	// invalid tool ?
	if( it == _tools.end() )	return 0;

	return it->second;
}

// ------------------------------------------------------------------------------------------------
std::vector<unsigned int> CadEngine::getSelectedObjects() const
{
	return _objManager->getSelectedObjects();
}

// ------------------------------------------------------------------------------------------------
void CadEngine::enableTool( int toolID )
{
	CadTools::iterator it = _tools.find( toolID );

	if( it == _tools.end() )
	{
		// invalid tool
		_currTool = 0;
		return;
	}

	_objManager->hilightObject(0);

	if( it->second->canCollectPoints() )
		_currTool = it->second; // set this tool as current in order to collect points
	else
	{
		// no points to be collected
		// just execute this tool
		it->second->execute();
		executeCommands();
	}
}

// ------------------------------------------------------------------------------------------------
bool CadEngine::isEnabled() const
{
	return _currTool != 0;
}

// ------------------------------------------------------------------------------------------------
void CadEngine::hilightObject( const OpenGL::Math::dvec3 &p )
{
	if( !isEnabled() )
		return;

	if( _currTool->canHilight() )
		_objManager->hilightObjectAt(p);
}


// ------------------------------------------------------------------------------------------------
void CadEngine::updateSizes( double worldSize, double pixelSize )
{
	_worldSize = worldSize;
	_pixelSize = pixelSize;
}

// ------------------------------------------------------------------------------------------------
void CadEngine::startRubberBand( const OpenGL::Math::dvec3 &p )
{
	if( !isEnabled() )
		return;

	_rubberband = true;

	_snapEngine.updateSnapHint(p,40*_pixelSize);


	OpenGL::Math::dvec3 closestPoint = p;
	
	if( _currTool->canSnap() )
		_snapEngine.getHint(closestPoint);

	_currTool->collectPoint( closestPoint );
}

// ------------------------------------------------------------------------------------------------
void CadEngine::updateRubberBand( const OpenGL::Math::dvec3 &p )
{
	if( !isEnabled() )
		return;

	_snapEngine.updateSnapHint(p,40*_pixelSize);

	if( _rubberband )
	{
		OpenGL::Math::dvec3 closestPoint = p;
		if( _currTool->canSnap() )
			_snapEngine.getHint(closestPoint);
	
		_currTool->dragPoint( closestPoint );
	}
}

// ------------------------------------------------------------------------------------------------
void CadEngine::endRubberBand( const OpenGL::Math::dvec3 &p )
{
	if( !isEnabled() )
		return;
	
	_snapEngine.updateSnapHint(p,40*_pixelSize);

	if( _rubberband )
	{
		OpenGL::Math::dvec3 closestPoint = p;
		if( _currTool->canSnap() )
			_snapEngine.getHint(closestPoint);

		_currTool->done(closestPoint,true);
		_rubberband = false;
	}
}

// ------------------------------------------------------------------------------------------------
void CadEngine::issueCommand( const Command &c, bool queued )
{
	if( queued ) _commands.push_back( c );
	else		 executeCommand( c );
}

// ------------------------------------------------------------------------------------------------
void CadEngine::executeCommands(  )
{
	for( size_t i=0; i<_commands.size(); ++i )
		executeCommand( _commands[i] );

	_commands.clear();
}

// ------------------------------------------------------------------------------------------------
void CadEngine::executeCommand( const Command &c )
{
	std::cout << "[CAD Engine] Command";

	switch( c.command )
	{
	case Create:  createGeometry( c );  break;
	case Select:  selectGeometry( c );  break;
	case Destroy: destroyGeometry( c ); break;
	}
}

// ------------------------------------------------------------------------------------------------
void CadEngine::createGeometry( const Command &c )
{	
	bool ret     = false;	
	VObject *obj = 0;
	
	switch( c.primitive )
	{
	case Point: obj = new VPoint( c.points[0] );			
				ret = _objManager->addObject( obj );
				std::cout << " CREATE POINT "; 
				break;
	
	case Line:  obj = new VLine( c.points[0],c.points[1] ); 
				ret = _objManager->addObject( obj );
				std::cout << " CREATE LINE ";
				break;

	case PolyLine:  obj = new VPolyLine( c.points ); 
				ret = _objManager->addObject( obj );
				std::cout << " CREATE POLYLINE ";
				break;
	
	case Quad:  obj = new VRectangle( OpenGL::Math::box3::createAABB(c.points[0],c.points[1]) );
				ret = _objManager->addObject( obj );
				std::cout << " CREATE QUAD ";
				break;

	case Circle: obj = new VCircle( c.points[0], OpenGL::Math::distance(c.points[0],c.points[1]) );
				 ret = _objManager->addObject( obj );
				 std::cout << " CREATE CIRCLE ";
				 break;
	}

	if( obj )
	{
		setAttributes(obj,c);
		
		_snapEngine.addPoints( obj->snapPoints(), obj->id() );


		Command undoCommand;
		undoCommand.command = Destroy;
		undoCommand.target =  obj->id();

		_undo.push_back( undoCommand );
	}

	std::cout << (ret ? "completed":"failed") << std::endl;
}

// ------------------------------------------------------------------------------------------------
void CadEngine::selectGeometry( const Command &c )
{
	bool ret = false;
	switch( c.primitive )
	{
	case Point: _objManager->selectObjectAt( c.points[0]  );			ret = true; std::cout << " SELECT POINT "; break;
	case Quad:  _objManager->selectArea( c.points[0],c.points[1]  );	ret = true; std::cout << " SELECT QUAD ";break;
	}

	std::cout << (ret ? "completed":"failed") << std::endl;
}

// ------------------------------------------------------------------------------------------------
void CadEngine::destroyGeometry( const Command &c ) 
{
	Command undoCommand; //TBD

	_snapEngine.removePoints( c.target );
	VObject *obj = _objManager->removeObject( c.target );

	std::cout << " DELETE completed"<< std::endl;


	//std::vector<unsigned int> selectedObjects = _objManager->getSelectedObjects();

	//for( size_t i=0; i<selectedObjects.size(); ++i )
	//	_snapEngine.removePoints( selectedObjects[i] );

	//_objManager->removeObjects( selectedObjects );

	//std::cout << " DELETE completed"<< std::endl;
}

// ------------------------------------------------------------------------------------------------
void CadEngine::setAttributes( VObject *obj, const Command &c )
{
	std::vector<std::string> attributes = split(c.attributes,";");

	for( size_t i=0; i<attributes.size(); ++i )
	{
		if	   ( startsWith(attributes[i], "Name:")  ) { obj->setName( after(attributes[i],":") );}
		else {}
	}

	obj->setColor( c.color );
}


// ------------------------------------------------------------------------------------------------
void CadEngine::undo()
{
	if( _undo.size() > 0 )
	{
		executeCommand( _undo.back() );
		_undo.pop_back();
	}
}

// ------------------------------------------------------------------------------------------------
void CadEngine::redo()
{}
